import collections
import heapq
import itertools
import random
import threading
import time
from time import sleep


class EventLoop(object):
    _instance = None

    @staticmethod
    def get_event_loop():
        if EventLoop._instance is None:
            EventLoop._instance = EventLoop()
        return EventLoop._instance

    def __init__(self):
        self._ready = collections.deque()
        self._delay = []
        self._isStop = False

    def call_soon(self, cb, *args):
        self._ready.append((cb, args))

    def call_latter(self, delay, cb, *args):
        t = time.time() + delay
        heapq.heappush(self._delay, (t, cb, args))

    def run_forever(self):
        while True:
            self.run_once()
            if self._isStop:
                break

    def stop(self):
        self._isStop = True

    def run_once(self):
        now = time.time()
        if self._delay:
            if self._delay[0][0] < now:
                _, cb, args = heapq.heappop(self._delay)
                self._ready.append((cb, args))

        num = len(self._ready)
        for i in range(num):
            cb, args = self._ready.popleft()
            cb(*args)


class Future(object):
    def __init__(self):
        self._result = None
        self._done = False
        self._callback = []
        self.loop = EventLoop.get_event_loop()

    def __await__(self):
        yield self
        return self.result()

    def set_result(self, result):
        if self._done:
            raise RuntimeError('future already done')
        self._done = True
        self._result = result
        for cb in self._callback:
            self.loop.call_soon(cb)

    def result(self):
        if self._done:
            return self._result
        else:
            raise RuntimeError('future is not done')

    def add_done_callback(self, cb):
        self._callback.append(cb)


task_id_counter = itertools.count(1)


class Task(Future):
    def __init__(self, coro):
        super().__init__()
        self.coro = coro
        loop.call_soon(self.run)
        self._id = f'Task-{next(task_id_counter)}'

    def run(self):
        print(f'----- {self._id} -----')
        if not self._done:
            try:
                x = self.coro.send(None)
            except StopIteration as e:
                self.set_result(e.value)
            else:
                assert isinstance(x, Future)
                # 什么时候能恢复
                x.add_done_callback(self.run)
        else:
            print('task is done')


def fake_io_read(future):
    def read():
        sleep(random.random()) # 假设io阻塞
        future.set_result(random.randint(1, 100))
    threading.Thread(target=read).start()


async def small_step():
    future = Future()
    fake_io_read(future)
    result = await future
    return result


if __name__ == '__main__':
    loop = EventLoop.get_event_loop()
    for i in range(10):
        t = Task(small_step())
    loop.call_latter(2.1, loop.stop)
    loop.run_forever()
